using System;

using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using libUsb;

namespace GarminUSBLib{
	public class DataReceivedEventArgs{
		public byte[] Bytes;
		public Exception ReceiverException;
	}
	public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
	
	public class GarminDriver: System.IDisposable{
		public static GarminDriver Instance;
		
		static GarminDriver(){
			Instance = new GarminDriver();
		}
		
		const int VENDOR_ID			= 0x91E;
		const int PRODUCT_ID 		= 0x3; 
		const string DEVICE_ID		= "vid_091E&pid_0003";
		const int API_VERSION		= 0x1;
		const int MAX_BUFFER_SIZE	= 4096;
		const int ASYNC_DATA_SIZE	= 64;
		
		USBSharp.USBSharp.GUID garminUsb;
		USBSharp.USBSharp device;
		bool stopping = false;
		
		protected Thread dataReadingThread;
		protected FileStream fs;
		
		public enum EPacketType: int {USB=0,APPLICATION=20};
		public enum EUSBProtocolPacketID: int {	Pid_Data_Available	= 2,
												Pid_Start_Session	= 5,
												Pid_Session_Started = 6};
		
		
		[StructLayout(LayoutKind.Sequential)]
		public struct Packet_t
		{
			public 	byte		packetType;
			private	byte		reserved1;
			private	byte		reserved2;
			private	byte		reserved3;
			public 	ushort		packetID;
			private ushort		reserved4;
			public 	uint		dataSize;
			public	byte[]		data;
		}
		public event DataReceivedEventHandler DataReceived;

		void CheckInitialization(){
			if (!IsInitialized){
				throw new SystemException();
			}
		}
		
		void InitDevice(int deviceId, string devicePath)
		{
			device = new USBSharp.USBSharp();

			device.CT_SetupDiEnumDeviceInterfaces(deviceId);
			int size = 0;
			int requiredSize = 0;

			int resultb = device.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, size);
			size = requiredSize;
			resultb = device.CT_SetupDiGetDeviceInterfaceDetailx(ref requiredSize, size);
			resultb = 0;
			resultb = device.CT_CreateFile(devicePath);
			
			if (resultb == 0)
				throw new SystemException("Can't create USB file");
		}
		void print_configuration(usb_config_descriptor des){
			
		}
		GarminDriver(){
			garminUsb.Data1 = 0x2C9C45C2;
			garminUsb.Data2 = 0x8E7D;
			garminUsb.Data3 = 0x4C08;
			garminUsb.data4 = new byte[] {0xa1,0x2d,0x81,0x6b,0xba,0xe7,0x22,0xc0};
			
			//NativeMethods.usb_set_debug(4);
			NativeMethods.usb_init();
			NativeMethods.usb_find_busses();
			NativeMethods.usb_find_devices();
			
			usb_bus bus;
			usb_device dev;
			
			for (bus = NativeMethods.usb_get_busses(); bus!=null; bus=bus.Next){
				for (dev = bus.Devices; dev != null; dev = dev.Next){
					int ret, i;
					char[] st = new char[256];
					IntPtr udev = NativeMethods.usb_open(dev);
					
					if (udev !=null){
						dev.descriptor.ToString();
						NativeMethods.usb_close(udev);
					}
					
					if (dev.config != null){
						continue;	
					}
					
					for (i =0; i < dev.descriptor.bNumConfigurations; i++){
						//print_configuration(dev.config);
					}
				}
			}
/*
			int my_device_count = 0;
			string my_device_path = string.Empty;
			using (USBSharp.USBSharp myUsb = new USBSharp.USBSharp())
			{
				//myUsb.CT_HidGuid();
				myUsb.CT_UsbGuid = garminUsb;
				myUsb.CT_SetupDiGetClassDevs();

				int result = -1;
				int device_count = 0;
				int size = 0;
				int requiredSize = 0;

				while (result != 0)
				{
					result = myUsb.CT_SetupDiEnumDeviceInterfaces(device_count);
					int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);
					size = requiredSize;
					resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetailx(ref requiredSize, size);

					if (myUsb.DevicePathName.IndexOf(DEVICE_ID) > 0)
					{
						my_device_count = device_count;
						my_device_path = myUsb.DevicePathName;
						break;
					}
					device_count++;
				}

				if (my_device_path == string.Empty)
				{
					Exception devNotFound = new Exception(@"Device could not be found.");
					throw (devNotFound);
				}
			}
			if (my_device_path != string.Empty)
			{
				InitDevice(my_device_count, my_device_path);
			}
				*/	
		}
		
		public void Dispose(){
			if (device != null) {
				device.Dispose();
				device = null;
			}
		}
		
		~GarminDriver(){
			Dispose();
		}
		
		public bool IsInitialized {
			get {
				return device != null;
			}
		}
		public void Start(){
			CheckInitialization();
			fs = new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle((IntPtr)device.HidHandle, false), FileAccess.Read, ASYNC_DATA_SIZE, true);
	
			byte[] buf = new byte[ASYNC_DATA_SIZE];
	
			object[] state = new object[2];
			state[0] = buf;
			state[1] = fs;
	
			IAsyncResult ar = fs.BeginRead(buf, 0, ASYNC_DATA_SIZE, new AsyncCallback(Read), state);
		}
		public void Stop()
		{
			CheckInitialization();
			stopping = true;
			if(fs != null)
			{
				fs.Close();
				fs = null;
			}
			device.CT_CloseHandle(device.HidHandle);
		}
		protected void Read(IAsyncResult ar)
		{
			CheckInitialization();
			object[] state = (object[])ar.AsyncState;
			byte[] buf = (byte[])state[0];

			if(!stopping)
			{
				DataReceivedEventArgs drea = new DataReceivedEventArgs();

				try
				{
					fs.EndRead(ar);	
					drea.Bytes = buf;
					fs.BeginRead(buf, 0, ASYNC_DATA_SIZE, new AsyncCallback(Read), state);
				}
				catch(IOException ioexc)
				{
					this.Stop();
					Exception devRemoved = new Exception(@"USB Device was removed.", ioexc);
					drea.ReceiverException = devRemoved;
				}

				OnDataReceived(drea);
			}
		}
		protected void OnDataReceived(DataReceivedEventArgs e)
		{
			CheckInitialization();
			if(DataReceived != null)
			{
				DataReceived(this, e);
			}
		}

		public void GetPacketSize(){
			CheckInitialization();
			byte[] buf = {0,0,0,0,5,0,0,0,0,0,0,0};
			USBSharp.USBSharp.OVERLAPPED ol = new USBSharp.USBSharp.OVERLAPPED();
			device.CT_WriteFile(buf,12,ref ol);
			//byte[] f = device.CT_ReadFile(16,ref ol);
			
			
		}
		public bool GetDeviceInfo()
		{
			CheckInitialization();
			return false;
		}
	}
}
